/**
 * Copyright (c) 2020-2021 ThunderSoft
 * All Rights Reserved by Thunder Software Technology Co., Ltd and its affiliates.
 * You may not use, copy, distribute, modify, transmit in any form this file
 * except in compliance with ThunderSoft in writing by applicable law.
 *
 */
/**
 * @file    hal_file.c
 * @brief   hal file funtion 
 * @details hal file funtion
 * @version 1.0
 * @author  Hu lei
 * @date    2021-03-05
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2021-03-05               Hu lei              Create it.
 *
 */
#include <stdio.h>
#include <stdbool.h>
#include <cmsis_os2.h>
#include <utils_file.h>
#include "lfs.h"
#include <ohos_init.h>
#ifndef LFS_THREADSAFE
#error "Must enable LFS_THREADSAFE"
#endif

#define MAX_FD 32
#define BITS_PER_UL (sizeof(unsigned long) * 8)
#define BITMAP_SIZE ((MAX_FD + BITS_PER_UL - 1) / BITS_PER_UL)

#define DEV_INFO            "hilink_dev_info"
#define TIMER_INFO          "hilink_timer_info"
#define DEFAULT_FLASH_SIZE  258

static unsigned char hilink_dev_info = 0;
static unsigned char hilink_fd = -1;
static unsigned char hilink_timer_info = 0;
static unsigned int  hilink_file_addr = 0;

static struct lfs_config g_cfg;
static struct lfs g_lfs;
static struct lfs_file g_files[MAX_FD];
static unsigned long g_files_bitmap[BITMAP_SIZE];
static osMutexId_t g_mutex;

static char tempbuffer[1600];

extern int bl_flash_erase(uint32_t addr, int len);
extern int bl_flash_write(uint32_t addr, uint8_t *src, int len);
extern int bl_flash_read(uint32_t addr, uint8_t *dst, int len);

static int littlefs_lock(const struct lfs_config *c)
{
    (void)c;
    osMutexAcquire(g_mutex, 0xffffffff);

    return 0;
}

static int littlefs_unlock(const struct lfs_config *c)
{
    (void)c;
    osMutexRelease(g_mutex);
    return 0;
}

int littlefs_init(int size)
{
    // first call vendor littlefs_config_init
    extern void littlefs_config_init(struct lfs_config *cfg);
    littlefs_config_init(&g_cfg);
    if(size){
        g_cfg.read_size = size;
        g_cfg.prog_size = size;
        g_cfg.cache_size = size;
        g_cfg.lookahead_size = size;
    }

    // implement threadsafe
    g_cfg.lock = littlefs_lock;
    g_cfg.unlock = littlefs_unlock;

    int err = lfs_mount(&g_lfs, &g_cfg);
    if (err)
    {
        err = lfs_format(&g_lfs, &g_cfg);
        if (err)
        {
            return -1;
        }
        err = lfs_mount(&g_lfs, &g_cfg);
        if (err)
        {
            printf("second mount fail, err = %d\r\n", err);
	    return -1;
        }
    }
    printf("littlefs mounted\r\n");
    return 0;
}

//CORE_INIT(littlefs_init);

static struct lfs_file *_FdToFile(int fd)
{
    if (fd < 0 || fd >= MAX_FD)
    {
        return NULL;
    }
    int idx = fd / BITS_PER_UL;
    int bit = fd % BITS_PER_UL;
    if ((g_files_bitmap[idx] & (1 << bit)) == 0)
    {
        printf("_FdToFile: fd not allocated. fd = %d\r\n", fd);
        return NULL;
    }
    return &g_files[fd];
}

static int _GetFd(void)
{
    int fd = -1;
    littlefs_lock(&g_cfg);
    for (unsigned int i = 0; i < BITMAP_SIZE; i++)
    {
        unsigned long tmp = ~g_files_bitmap[i];
        int bit = __builtin_ffsl(tmp);
        if (bit)
        {
            --bit;
            fd = i * BITS_PER_UL + bit;
            if (fd < MAX_FD)
            {
                g_files_bitmap[i] |= (1 << bit);
            }
            else
            {
                fd = -1;
            }
            break;
        }
    }
    littlefs_unlock(&g_cfg);
    return fd;
}

static void _PutFd(int fd)
{
    if (fd < 0 || fd >= MAX_FD)
    {
        // printf("_PutFd fail: fd=%d\r\n", fd);
        return;
    }
    int idx = fd / BITS_PER_UL;
    int bit = fd % BITS_PER_UL;
    littlefs_lock(&g_cfg);
    if (g_files_bitmap[idx] & (1 << bit))
    {
        g_files_bitmap[idx] &= ~(1ul << bit);
    }
    else
    {
        // printf("_PutFd fail: fd=%d\r\n", fd);
    }
    littlefs_unlock(&g_cfg);
}

static struct lfs_file *FdToFile(int fd)
{
    if (fd > 0)
    {
        --fd;
    }
    return _FdToFile(fd);
}

static int GetFd(void)
{
    int fd = _GetFd();
    if (fd >= 0)
    {
        ++fd;
    }
    return fd;
}

static void PutFd(int fd)
{
    if (fd > 0)
    {
        --fd;
    }
    _PutFd(fd);
}

static void PrintLfsFile(const char *hint, const struct lfs_file *file)
{
    (void)hint;
    (void)file;
    // printf("%s: <file %p> id = %d  flags = %08x\r\n", hint, file, file->id, file->flags);
}

int HalFileOpen(const char* path, int oflag, int mode)
{
    (void)mode;
    int fd = GetFd();
    int lfs_oflag = 0;

    if(!strcmp(path, DEV_INFO)){
        printf("open file path:%s\r\n", path);
        hilink_dev_info = 1; 
        hilink_fd = fd;
    }

    if ((oflag & O_RDONLY_FS) == O_RDONLY_FS)
    {
        lfs_oflag |= LFS_O_RDONLY;
    }
    if ((oflag & O_WRONLY_FS) == O_WRONLY_FS)
    {
        lfs_oflag |= LFS_O_WRONLY;
    }
    if ((oflag & O_RDWR_FS) == O_RDWR_FS)
    {
        lfs_oflag |= LFS_O_RDWR;
    }
    if ((oflag & O_CREAT_FS) == O_CREAT_FS)
    {
        lfs_oflag |= LFS_O_CREAT;
    }
    if ((oflag & O_EXCL_FS) == O_EXCL_FS)
    {
        lfs_oflag |= LFS_O_EXCL;
    }
    if ((oflag & O_TRUNC_FS) == O_TRUNC_FS)
    {
        lfs_oflag |= LFS_O_TRUNC;
    }
    if ((oflag & O_APPEND_FS) == O_APPEND_FS)
    {
        lfs_oflag |= LFS_O_APPEND;
    }

    struct lfs_file *file = FdToFile(fd);
    if (!file)
    {
        return -1;
    }
    int err = lfs_file_open(&g_lfs, file, path, lfs_oflag);
    if (err)
    {
        PutFd(fd);
        return -1;
    }
    hilink_file_addr = (unsigned int)(file);
    PrintLfsFile("open", file);
    //printf("open file end\r\n");
    return fd;
}

int HalFileClose(int fd)
{
    struct lfs_file *file = FdToFile(fd);
    if (!file)
    {
        return -1;
    }

    //printf("start close file\r\n");
    if(hilink_dev_info == 1 && hilink_fd == fd && (hilink_file_addr == (unsigned int)(file))) {
        //printf("hilink dev info close\r\n");
        hilink_dev_info = 0;
        hilink_fd = -1;
    }

    PrintLfsFile("close", file);
    int err = lfs_file_close(&g_lfs, file);
    if (!err)
    {
        PutFd(fd);
    }
    else
    {
        err = -1;
    }

    //printf("end close file\r\n");
    return err;
}

int HalFileRead(int fd, char *buf, unsigned int len)
{
    struct lfs_file *file = FdToFile(fd);
    int ret = -1;

    if (!file)
    {
        return -1;
    }
    //printf("start read file\r\n");
    PrintLfsFile("read", file);
    ret = (int)lfs_file_read(&g_lfs, file, buf, len);

    if (0 == strcmp(buf, "") || ret < 0) {
        if(hilink_dev_info == 1 && hilink_fd == fd && (hilink_file_addr == (unsigned int)(file))) {
            //printf("hilink read buf:%s, len:%d\r\n", buf, len);
            if (0 == strcmp(buf, "")) {
                //printf("Read hilink info is NULL, try to read flash\r\n");

                unsigned int net_addr = 0x1FE000;
                bl_flash_read(net_addr, (unsigned char *)buf, len);
                if (0 == strcmp(buf, "")) {
                    unsigned int net_addr = 0x1FF000;
                    bl_flash_read(net_addr, (unsigned char *)buf, len);
                }
                buf[len+1] = '\0';
                //printf("read hilink info:%s\r\n", buf);
            }
        }
    }
    //printf("end read file\r\n");

    return ret;

}

int HalFileWrite(int fd, const char *buf, unsigned int len)
{
    struct lfs_file *file = FdToFile(fd);
    int ret = -1;

    if (!file)
    {
        return -1;
    }
    //printf("start write file\r\n");
    PrintLfsFile("write", file);
    ret = (int)lfs_file_write(&g_lfs, file, buf, len);

    if(hilink_dev_info == 1 && hilink_fd == fd && (hilink_file_addr == (unsigned int)(file))) {
        //printf("hilink write buf:%s, len:%d\r\n", buf, len);
        unsigned int net_addr = 0x1FE000;
        bl_flash_erase(net_addr, 1600);
        bl_flash_write(net_addr, (unsigned char *)tempbuffer, 1600);
        bl_flash_read(net_addr, (unsigned char *)tempbuffer, 1600);
        tempbuffer[1599] = '\0';
        //printf("check write hilink info:%s\r\n", tempbuffer);

        net_addr = 0x1FF000;
        bl_flash_erase(net_addr, 1600);
        bl_flash_write(net_addr, (unsigned char *)tempbuffer, 1600);
    }
    //printf("end write file\r\n");

    return ret;
}

int HalFileDelete(const char *path)
{
    int err = lfs_remove(&g_lfs, path);
    if (err)
    {
        return -1;
    }
    return 0;
}

int HalFileStat(const char *path, unsigned int *fileSize)
{
    struct lfs_info info;
    int err = lfs_stat(&g_lfs, path, &info);
    if (err)
    {
        return -1;
    }

    printf("info.name:%s---info.size:%d---info.type:%d\r\n", info.name, info.size, info.type);

    *fileSize = (unsigned int)info.size;
    return 0;
}

int HalFileSeek(int fd, int offset, unsigned int whence)
{
    switch (whence)
    {
    case SEEK_SET_FS:
        whence = LFS_SEEK_SET;
        break;
    case SEEK_CUR_FS:
        whence = LFS_SEEK_CUR;
        break;
    case SEEK_END_FS:
        whence = LFS_SEEK_END;
        break;
    default:
        printf("unknown whence: %u\r\n", whence);
        return -1;
    }

    struct lfs_file *file = FdToFile(fd);
    if (!file)
    {
        return -1;
    }

    if (lfs_file_seek(&g_lfs, file, (lfs_soff_t)offset, whence) < 0) {
        return -1;
    }
    return 0;
}
